##******************************************************************************
## Declaration operations in GP
##******************************************************************************
import vnnlp

class Genetic:
    '''
    '''
    def __init__(self, term, rand, poplen=100, pcross=0.95,
                 pmutate=0.05, pcopy=0):
        '''
        @term: instance of class Term
        @poplen: population length, default = 100
        @pcross: probability for crossover operation, default=0.95
        @pmutate: probability for mutation operation, default = 0.05
        @pcopy
        @oldpop: old population
        @newpop: new population
        '''
        self.term = term
        self.poplen = poplen
        self.pcross = pcross
        self.pmutate = pmutate
        self.pcopy = pcopy
        self.oldpop = []
        self.newpop = []
        self.random = rand
            
##    def __del__(self):
##        del self.term
##        for i in range(len(self.oldpop)):
##            del self.oldpop[i]
##        for i in range(len(self.newpop)):
##            del self.newpop[i]
        
    def GrowthTreeGen(self, maxdepth):
        '''
        Generate tree using growth method
        @maxdepth: maximum depth of tree
        '''
        
        if (maxdepth == 0):# to the limit then choose a terminal
            i = self.random.randint(0, self.term.tcard - 1)
            node = vnnlp.pygp.Node(self.term.terminals[i][0], [], vnnlp.pygp.VOIDVALUE)
            return node
        else:
            i = self.random.randint(0, self.term.termcard-1)
            node = vnnlp.pygp.Node(self.term.terms[i][0], [], vnnlp.pygp.VOIDVALUE)                       
            if (self.term.terms[i][1] > 0): #if it is function
                for k in range(self.term.terms[i][1]):
                    node.children.append(self.GrowthTreeGen(maxdepth-1))
            return node

    def FullTreeGen(self, maxdepth):
        '''
        Generate tree using full method
        @maxdepth: maximum depth of tree
        '''
        if (maxdepth == 0): #to the limit then choose a terminal
            i = self.random.randint(0, self.term.tcard - 1)
            node = vnnlp.pygp.Node(self.term.terminals[i][0], [], vnnlp.pygp.VOIDVALUE)
            return node
        else: #choosing from function
            i = self.random.randint(0, self.term.fcard - 1)
            node = vnnlp.pygp.Node(self.term.functions[i][0], [], vnnlp.pygp.VOIDVALUE)                       
            if (self.term.functions[i][1] > 0): #if it is function
                for k in range(self.term.functions[i][1]):
                    node.children.append(self.FullTreeGen(maxdepth-1))
            return node

    def RampedInit(self, depth, percentage):
        '''
        Ramp initialization with a percentage of full tree
        @percentage: 
        '''
        for i in range(self.poplen):
            #choose randomly from the function set
            j = self.random.randint(0, self.term.fcard - 1)
            node = vnnlp.pygp.Node(self.term.functions[j][0], [], vnnlp.pygp.VOIDVALUE)
            if (self.random.random() < percentage): #growth
                for k in range(self.term.functions[j][1]):
                    node.children.append(self.GrowthTreeGen(depth-1))
            else:
                for k in range(self.term.functions[j][1]):
                    node.children.append(self.FullTreeGen(depth-1))

            individual = vnnlp.pygp.Individual(node, node.GetSize(True), node.GetHeight(), node.GetAVGNode(), 0)
            self.oldpop.append(individual)
    

    def AdjustFitness(self):
        '''
        This function computes Adjusted Fitness
        This assumes that the fitness of an individual
        is already standardized (it means the smaller the better)
        '''
        for i in range(self.poplen):
            self.oldpop[i].fitness = 1.0 / (1.0 + self.oldpop[i].fitness)

    def NormalizeFitness(self):
        '''
        This function computes normalized fitness it  *
        supposes that the fitness of an individual is *
        already adjusted (it means between 0 - 1 and  *
        The bigger the better)                        *
        '''
        summ = 0
        for i in range(self.poplen):
            summ += self.oldpop[i].fitness
        for i in range(self.poplen):
            self.oldpop[i].fitness = self.oldpop[i].fitness/summ;

    #*************************************************
    #Selections works on Normalized Fitness
    #**************************************************

    def ProSelect(self):
        '''
        Propotionate Selection
        '''
        k = self.random.random()
        i = 0
        while i < self.poplen - 1 and k > 0:
            k -= self.oldpop[i].fitness
            if (k > 0):
                i += 1
        return i

    def TourSelect(self, size):
        '''
        Tournement Selection
        '''
        assert(size <= vnnlp.pygp.MAXTOUR)
        maxi = -1000
        pos = 0
        for i in range(size):
            j = self.random.randint(0, self.poplen-1)
            if self.oldpop[j].fitness > maxi:
                maxi = self.oldpop[j].fitness
                pos = j
        return pos

    #*************************************************
    #Genetic Operations
    #**************************************************

    def Crossover(self, parent1, parent2):
        '''
        Crossover selection
        @parent1: C{Individual}
        @parent2: C{Individual}
        @return: [child1, child2] - child1 & child2 is instances of Individual
        '''
        count = 0
        while count < vnnlp.pygp.MAXATEMPT:
            #copy the parents
            temp1 = parent1.Copy(True)
            temp2 = parent2.Copy(True)
            #attemp to make crossover
            #get 2 point in parent1 and parent2
            #print "parent1.size: ", temp1.size
            #print "parent2.size: ", temp2.size
            crosspoint1 = self.random.randint(2, temp1.size)
            crosspoint2 = self.random.randint(2, temp2.size)
            #Locate to crosspoint
            node1 = temp1.chrom[crosspoint1][0]
            node2 = temp2.chrom[crosspoint2][0]
            #try to do crossover
            #first swaping contents (name and value)
            temp = node1.name       #name
            node1.name = node2.name
            node2.name = temp
            temp = node1.value      #value
            node1.value = node2.value
            node2.value = temp
            #then swap the children
            temp = node1.children
            node1.children = node2.children
            node2.children = temp
            #compute the height after that
            temp1.height = temp1.chrom.GetHeight()
            temp2.height = temp2.chrom.GetHeight()
            if temp1.height <= vnnlp.pygp.MAXDEPTH and temp2.height <= vnnlp.pygp.MAXDEPTH:
                temp1.size = temp1.chrom.GetSize(True)
                temp1.branch = temp1.chrom.GetAVGNode()
                temp2.size = temp2.chrom.GetSize(True)
                temp2.branch = temp2.chrom.GetAVGNode()
                child1 = temp1.Copy(False)
                child2 = temp2.Copy(False)
                return [child1, child2]
            else:
                del temp1.chrom
                del temp2.chrom
            count += 1
        return None
            
    def Mutation (self, child, maxdepth, method):
        '''
        Mutation selection
        @child: C{Individual}
        @maxdepth: the depth of the new subtree
        @method: growth or full
        return new individual
        '''
        count = 0
        while count < vnnlp.pygp.MAXATEMPT:
            temp = child.Copy(True)
            replacepoint = self.random.randint(2, temp.size)
            replacenode = temp.chrom[replacepoint]
            node1 = replacenode[0]
            parentnode1 = replacenode[1]
            #generate new subtree
            new_subtree = None
            if(method == 'growth'):
                new_subtree = self.GrowthTreeGen(maxdepth)
            else:
                new_subtree = self.FullTreeGen(maxdepth)
##            for i in range(len(parentnode1.children)):
##                if(parentnode1.children[i].Compare(node1)):#if equal
##                    parentnode1.children[i] = new_subtree
##            del node1
            node1.name = new_subtree.name
            node1.value = new_subtree.value
            node1.children = new_subtree.children
            del new_subtree
            
            temp.height = temp.chrom.GetHeight()
            if(temp.height <= vnnlp.pygp.MAXDEPTH):
                del child.chrom
                child.chrom = temp.chrom
                child.height = temp.height
                child.size = child.chrom.GetSize(True)
                child.branch = child.chrom.GetAVGNode()
                return child
            del temp
            count += 1
        return None
        
